/*
This file is part of IRCaBot.
IRCaBot is IRC logger with features.
Source code: https://notabug.org/acetone/ircabot.
Copyright (C) acetone, 2023.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

#include "httpdocument.h"
#include "versioninformation.h"
#include "htmldocumenttemplate.h"

#include <QMapIterator>
#include <QJsonDocument>
#include <QCryptographicHash>
#include <QDebug>
#include <QDateTime>

#define LOG_DEBUG qDebug() << "HttpDocument:"

void HttpDocument::setCode(const QString &code)
{
    m_code = code;
}

void HttpDocument::setHeader(const QString &name, const QString &value)
{
    if (name.isEmpty() or value.isEmpty())
    {
        qCritical() << "HTTP header setting rejected (empty value):" << name << ";" << value;
        return;
    }

    m_headers.insert(name, value);
}

void HttpDocument::setBody(const QByteArray &body)
{
    m_body = body;
}

void HttpDocument::setBody(const QString &body, const QString &oldETag)
{
    QString eTag = QCryptographicHash::hash(body.toUtf8(), QCryptographicHash::Md5)
            .toBase64(QByteArray::Base64Option::Base64Encoding | QByteArray::Base64Option::OmitTrailingEquals).mid(0,5);

    if (oldETag == eTag)
    {
        LOG_DEBUG << "Data cached by user (not file on disk) / Hash:" << oldETag;

        setCode(http::CODE_304);
        return;
    }

    setBody(body.toUtf8());
    setHeader(http::HEADER_ETag, eTag);
}

void HttpDocument::setBodyStandardError(const QString &text, const QString &title, bool systemError, const QString &backLink)
{
    HTMLDocumentTemplate html(":/private/html/error/error.html");

    QFile pic (systemError ? ":/private/html/error/system_error.png" : ":/private/html/error/userside_error.png");
    if (not pic.open(QIODevice::ReadOnly))
    {
        qCritical() << "HttpDocument::setBodyStandardError() embedded picture not opened";
    }
    html.setValue("IMG_BASE64", pic.readAll().toBase64());
    if (pic.isOpen()) pic.close();

    html.setValue("TITLE", title);
    html.setValue("TEXT", text);
    html.setValue("SOURCES_LINK", info::SOURCE_CODE);
    html.setValue("SOURCES_NAME", QString(info::NAME) + " " + QString(info::VERSION));

    if (backLink.isEmpty())
    {
        html.setValue("BACK_LINK", "/index.php");
        html.setValue("BACK_BUTTON_LABEL", "OK");
    }
    else
    {
        html.setValue("BACK_LINK", backLink);
        html.setValue("BACK_BUTTON_LABEL", "GO BACK");
    }

    m_body = html.data();
}

void HttpDocument::setBody(const QJsonObject &body)
{
    m_body = QJsonDocument(body).toJson(QJsonDocument::JsonFormat::Compact);
    setHeader(http::HEADER_ContentType, http::CONTENT_TYPE_JSON);
}

void HttpDocument::setBody(QFile&& payload, const QString& oldETag)
{
    if (not payload.exists())
    {
        LOG_DEBUG << "File" << payload.fileName() << "not found";

        setCode(http::CODE_404);
        setBodyStandardError("Requested file not found", "Not found");
        return;
    }

    if (not payload.open(QIODevice::ReadOnly))
    {
        LOG_DEBUG << "File" << payload.fileName() << "reading failed";

        setCode(http::CODE_500);
        setBodyStandardError("Requested file exists, but reading failed", "Internal error");
        return;
    }

    QByteArray fileBytes = payload.readAll();
    payload.close();

    QString eTag = QCryptographicHash::hash(fileBytes, QCryptographicHash::Md5)
            .toBase64(QByteArray::Base64Option::Base64Encoding | QByteArray::Base64Option::OmitTrailingEquals).mid(0,5);

    if (oldETag == eTag)
    {
        LOG_DEBUG << "File cached by user:" << payload.fileName() << "/ Hash:" << oldETag;

        setCode(http::CODE_304);
        return;
    }

    setBody(fileBytes);
    setHeader(http::HEADER_ETag, eTag);

    setHeader(http::HEADER_ContentType,
              payload.fileName().endsWith(".html")  ? http::CONTENT_TYPE_HTML :
              payload.fileName().endsWith(".css")   ? http::CONTENT_TYPE_CSS :
              payload.fileName().endsWith(".js")    ? http::CONTENT_TYPE_JS :
              payload.fileName().endsWith(".ico")   ? http::CONTENT_TYPE_ICO :
              payload.fileName().endsWith(".svg")   ? http::CONTENT_TYPE_SVG :
              payload.fileName().endsWith(".png")   ? http::CONTENT_TYPE_PNG :
              payload.fileName().endsWith(".woff")  ? http::CONTENT_TYPE_WOFF :
              payload.fileName().endsWith(".woff2") ? http::CONTENT_TYPE_WOFF2 :
              payload.fileName().endsWith(".json")  ? http::CONTENT_TYPE_JSON :
              payload.fileName().endsWith(".mp3")   ? http::CONTENT_TYPE_MP3 :
              payload.fileName().endsWith(".gif")   ? http::CONTENT_TYPE_GIF :
              payload.fileName().endsWith(".txt")   ? http::CONTENT_TYPE_TEXT :
                                                      http::CONTENT_TYPE_BIN);
}

QByteArray HttpDocument::document() const
{
    constexpr const char HTML_NEWLINE[] {"\r\n"};

    QByteArray result = "HTTP/1.0 " + code().toUtf8() + HTML_NEWLINE;

    QMapIterator<QString, QString> iter( headers() );
    while (iter.hasNext())
    {
        iter.next();
        result += (iter.key()+": "+iter.value()+HTML_NEWLINE).toUtf8();
    }

    result += QString( QString(http::HEADER_ContentLength)  + ": " +
              QString::number( body().size()).toUtf8() ).toUtf8() + HTML_NEWLINE;
    result += HTML_NEWLINE;

    result += body();

    return result;
}
